import time
import os
import struct
import serial
import serial.tools.list_ports as list_ports

CMD_UPDATE_STA  = 0
CMD_UPDATE_TEMP = 1

TEMP_HOTEND = 0
TEMP_BED    = 1

STA_UNCONNECTED     = 0
STA_CONNECTED       = 1
STA_TESTING         = 2
STA_DISCONNECTED    = 3
STA_TEST_PASS       = 4
STA_NO_MC           = 5
STA_NO_EXECUTOR     = 6
STA_BAD_BED_PORT    = 7
STA_EXCTR_HEATED_FAIL = 8
STA_BED_HEATED_FAIL = 9
STA_VERSION_ERROR   = 10
STA_EXCTR_TYPE_ERR  = 11
STA_SET_TEMP_ERR    = 12
STA_MEASURE_TEMP_ERR = 13
STA_MEASURE_EXCTR_ERR = 14
STA_MEASURE_BED_ERR = 15
STA_HEATED_FAIL = 16
STA_PROCESS_DEAD = 17
STA_TEST_FAIL = 18
STA_HOMING = 19
STA_NO_LINEAR = 20
STA_NO_MODEL = 21
STA_NO_USB_STICK = 22
STA_NO_CHAMBER = 23
STA_HOMING_ADDON = 24
STA_MAX = 25

# macro about HMI
COLOR_RED   = 63488
COLOR_BLUE  = 31
COLOR_GRAY  = 33840
COLOR_BLACK = 0
COLOR_WHITE = 65535
COLOR_GREEN = 2016
COLOR_ORANGE = 48192
COLOR_YELLOW = 65504

class HmiTjc(object):
    sta_info = [x for x in range(0, STA_MAX)]
    sta_info[STA_UNCONNECTED] = '"等待连接"'
    sta_info[STA_CONNECTED] = '"已连接，主控启动中"'
    sta_info[STA_TESTING] = '"测试中。。。"'
    sta_info[STA_DISCONNECTED] = '"已断开"'
    sta_info[STA_TEST_PASS] = '"测试通过"'
    sta_info[STA_NO_MC] = '"主控启动失败:F100"'
    sta_info[STA_NO_EXECUTOR] = '"识别不到执行头:F101"'
    sta_info[STA_BAD_BED_PORT] = '"热床端口损坏:F102"'
    sta_info[STA_EXCTR_HEATED_FAIL] = '"执行头加热失败:F103"'
    sta_info[STA_BED_HEATED_FAIL] = '"热床加热失败:F104"'
    sta_info[STA_VERSION_ERROR] = '"固件版本错误:F105"'
    sta_info[STA_EXCTR_TYPE_ERR] = '"执行头类型错误:F106"'
    sta_info[STA_SET_TEMP_ERR] = '"温度设置失败:F107"'
    sta_info[STA_MEASURE_TEMP_ERR] = '"热床和喷嘴测温异常:F108"'
    sta_info[STA_MEASURE_EXCTR_ERR] = '"喷嘴测温异常:F109"'
    sta_info[STA_MEASURE_BED_ERR] = '"热床测温异常:F110"'
    sta_info[STA_HEATED_FAIL] = '"执行头和热床加热失败:F111"'
    sta_info[STA_PROCESS_DEAD] = '"测试工具异常，请联系技术员！"'
    sta_info[STA_TEST_FAIL] = '"测试失败:F112"'
    sta_info[STA_HOMING] = '"模组正在回原点"'
    sta_info[STA_NO_LINEAR] = '"识别不到直线模组:F113"'
    sta_info[STA_NO_MODEL] = '"未识别到3根模组:F114"'
    sta_info[STA_NO_USB_STICK] = '"未识别到U盘:F115"'
    sta_info[STA_NO_CHAMBER] = '"未识别到外罩板:F119"'
    sta_info[STA_NO_CHAMBER] = '扩展口模组正在回原点'

    sta_color = [x for x in range(0, STA_MAX)]
    sta_color[STA_UNCONNECTED] = COLOR_YELLOW
    sta_color[STA_CONNECTED] = COLOR_BLUE
    sta_color[STA_TESTING] = COLOR_BLUE
    sta_color[STA_DISCONNECTED] = COLOR_YELLOW
    sta_color[STA_TEST_PASS] = COLOR_GREEN
    sta_color[STA_NO_MC] = COLOR_RED
    sta_color[STA_NO_EXECUTOR] = COLOR_RED
    sta_color[STA_BAD_BED_PORT] = COLOR_RED
    sta_color[STA_EXCTR_HEATED_FAIL] = COLOR_RED
    sta_color[STA_BED_HEATED_FAIL] = COLOR_RED
    sta_color[STA_VERSION_ERROR] = COLOR_RED
    sta_color[STA_EXCTR_TYPE_ERR] = COLOR_RED
    sta_color[STA_SET_TEMP_ERR] = COLOR_RED
    sta_color[STA_MEASURE_TEMP_ERR] = COLOR_RED
    sta_color[STA_MEASURE_EXCTR_ERR] = COLOR_RED
    sta_color[STA_MEASURE_BED_ERR] = COLOR_RED
    sta_color[STA_HEATED_FAIL] = COLOR_RED
    sta_color[STA_PROCESS_DEAD] = COLOR_RED
    sta_color[STA_TEST_FAIL] = COLOR_RED
    sta_color[STA_HOMING] = COLOR_BLUE
    sta_color[STA_NO_LINEAR] = COLOR_RED
    sta_color[STA_NO_MODEL] = COLOR_RED
    sta_color[STA_NO_USB_STICK] = COLOR_RED
    sta_color[STA_NO_CHAMBER] = COLOR_RED
    sta_color[STA_HOMING_ADDON] = COLOR_BLUE

    E_INV_CMD         = 0x0
    E_SUCCESS         = 0x1
    E_INV_ID_WIDGET   = 0x2
    E_INV_ID_PAGE     = 0x3
    E_INV_ID_PIC      = 0x4
    E_INV_ID_FONT     = 0x5
    E_FAIL_FILE_OP    = 0x6
    E_INV_CRC         = 0x9
    E_INV_BAUDRATE    = 0x11
    E_INV_ID_WAVE     = 0x12
    E_INV_VAR_NAME    = 0x1A
    E_INV_VAR_OP      = 0x1B
    E_INV_ASSIGN_OP   = 0x1C
    E_FAIL_EEPROM_OP  = 0x1D
    E_INV_PARAM_NUM   = 0x1E
    E_FAIL_IO_OP      = 0x1F
    E_FAIL_ESCAPE     = 0x20
    E_LONG_VAR_NAME   = 0x23
    E_FIFO_OVERFLOW   = 0x24

    TOUCH_EVENT_PRESSED  = 1
    TOUCH_EVENT_RELEASED = 0

    RET_TOUCH_AREA_EVENT    = 0X65
    RET_CURRENT_PAGE_ID     = 0X66
    RET_TOUCH_POSITION      = 0X67
    RET_TOUCH_WHEN_SLEEP    = 0X68
    RET_STR                 = 0X70
    RET_NUM                 = 0X71
    RET_DEV_SLEPT_AUTO      = 0X86
    RET_DEV_WAKUP_AUTO      = 0X87
    RET_DEV_BOOTUP          = 0X88
    RET_UPGRADING_WITH_SD   = 0X89
    RET_TRANSFER_DONE       = 0XFD
    RET_TRANSFER_READY      = 0XFE

    def __init__(self, port="/dev/ttyS0", baudrate=9600):
        #ser_list = sorted(ports.device for ports in list_ports.comports())
        #if port not in ser_list:
        #    raise Exception("No such port: " + port)
        if not os.path.exists(port):
            raise RuntimeError("No such port: " + port)

        self.stabtn = {x:"c%sbtn"%x for x in range(1, 5)}
        self.statxt = {x:"c%ssta"%x for x in range(1, 5)}

        self.hotend_temp = {x:"c%setemp"%x for x in range(1, 5)}
        self.bed_temp = {x:"c%sbtemp"%x for x in range(1, 5)}

        self._debug = False
        self._port = port
        self._baudrate = baudrate
        self._current_status = STA_UNCONNECTED
        self._opened = False

    def dump_packet(self, data, receive=True):
        if self._debug:
            print("%s Packet data[%s]: " % (("<-" if receive else "->"), len(data) - 3), end=' ')
            l = [hex(int(b)) for b in data]
            print(" ".join(l))

    def read_bytes_safe(self, num):
        data = self.ser.read(num)
        if len(data) != num:
            raise serial.SerialTimeoutException("read timeout")

        return data

    def read_packet(self):
        # read and check frame start magic
        packet = bytes()
        i = 0
        end = 3
        while end > 0:
            packet += self.read_bytes_safe(1)
            if packet[i] != 0xFF:
                i += 1
                continue
            else:
                end -= 1

        self.dump_packet(packet, receive=True)

        # payload only is returned
        return packet

    def write_packet(self, data):
        # frame start and direction magic
        packet = str(data).encode('gb2312')
        # end code
        packet += bytes([0xff, 0xff, 0xff])
        self.ser.write(packet)
        self.ser.flush()
        self.dump_packet(packet, receive=False)

    def disconnect(self):
        if self._opened:
            self.ser.close()
            self._opened = False

    def connect(self):
        try:
            self.ser = serial.Serial(port=self._port, baudrate=self._baudrate, parity=serial.PARITY_NONE)
            self.ser.timeout = 10
            self.ser.write_timeout = 10
            self.ser.inter_byte_timeout = 0.5
            self.ser.flushInput()
            self._opened = True
        except Exception as e:
            print("failed to open UART: " + e)
            pass

        ret = self.get_attr("get dim")
        if ret == None:
            print("invalid return var type")
            return 1

        self.set_attr("bkcmd=3")

        self.set_attr("page page1")

        return 0

    def set_attr(self, cmd):
        self.write_packet(cmd)
        recv = self.read_packet()
        if recv[0] != 1:
            print("HMI return err [%s] for command [%s]" % (recv[0], cmd))

    def get_attr(self, cmd):
        self.write_packet(cmd)
        recv = self.read_packet()

        ret = None
        if recv[0] == self.RET_NUM:
            # little ending, 4 bytes
            ret = struct.unpack('<I', recv[1:5])[0]
        elif recv[0] == self.RET_STR:
            ret = str(recv[1:-3])
        elif recv[0] < 0x65:
            print("invalid cmd [%s], HMI return code: %s" % (cmd, recv[0]))
        else:
            print("HMI return {0} for cmd: {1}".format(int(recv[0]), cmd))
        self.ser.flushInput()
        return ret

    def update_temp(self, ch=0, target=TEMP_HOTEND, temp=0):
        ch += 1
        if target == TEMP_HOTEND:
            self.set_attr("{0}.val={1}".format(self.hotend_temp[ch], int(temp)))
        elif target == TEMP_BED:
            self.set_attr("{0}.val={1}".format(self.bed_temp[ch], int(temp)))
        else:
            pass

    def update_sta(self, ch=0, sta=STA_UNCONNECTED):
        ch += 1
        if self._current_status == sta:
            print("same status, won't change")
            return
        self._current_status = sta
        # change color of button firstly
        self.set_attr("{0}.pco={1}".format(self.stabtn[ch], self.sta_color[sta]))
        self.set_attr("{0}.bco={1}".format(self.stabtn[ch], self.sta_color[sta]))

        # change info text
        self.set_attr("{0}.txt={1}".format(self.statxt[ch], self.sta_info[sta]))
        if self.sta_color[sta] == COLOR_RED:
            self.set_attr("{0}.pco={1}".format(self.statxt[ch], self.sta_color[sta]))
        elif self.sta_color[sta] == COLOR_BLUE:
            self.set_attr("{0}.pco={1}".format(self.statxt[ch], self.sta_color[sta]))
        else:
            self.set_attr("{0}.pco={1}".format(self.statxt[ch], COLOR_BLACK))

# test
if __name__ == '__main__':
    hmi = HmiTjc()
    hmi.connect()

    hmi.update_sta(ch=1, sta=STA_CONNECTED)
    hmi.update_sta(ch=2, sta=STA_TESTING)
    hmi.update_sta(ch=3, sta=STA_TEST_PASS)
    hmi.update_sta(ch=4, sta=STA_BED_HEATED_FAIL)

    hmi.update_temp(ch=1, target=TEMP_HOTEND, temp=100)
    hmi.update_temp(ch=2, target=TEMP_HOTEND, temp=100)
    hmi.update_temp(ch=3, target=TEMP_HOTEND, temp=100)
    hmi.update_temp(ch=4, target=TEMP_HOTEND, temp=100)

    hmi.update_temp(ch=1, target=TEMP_BED, temp=100)

    hmi.disconnect()
